package qq2564874169.tuaotuao.fx.job;

import org.apache.logging.log4j.Logger;
import qq2564874169.tuaotuao.fx.*;
import qq2564874169.tuaotuao.fx.context.BeanContext;
import qq2564874169.tuaotuao.fx.context.FxSpringContext;
import qq2564874169.tuaotuao.fx.lock.AppLockspace;
import qq2564874169.tuaotuao.fx.lock.Lockspace;
import qq2564874169.tuaotuao.fx.lock.LockspaceException;
import qq2564874169.tuaotuao.fx.log.FxLog;
import qq2564874169.tuaotuao.fx.utils.ExUtil;
import qq2564874169.tuaotuao.fx.utils.RandomUtil;
import qq2564874169.tuaotuao.fx.utils.StringUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TimeJob {
    private final static String rbKey = UUID.randomUUID().toString();
    private final static String raKey = UUID.randomUUID().toString();
    private final static String reKey = UUID.randomUUID().toString();
    private static Logger logger = FxLog.getLogger(TimeJob.class);
    private static FxSpringContext beanContext;
    private static List<Class<?>> stopJob = new ArrayList<>();
    public static EventDispatcher<RunBefore> evBefore = new EventDispatcher<>(rbKey);
    public static EventDispatcher<RunAfter> evAfter = new EventDispatcher<>(raKey);
    public static EventDispatcher<RunError> evError = new EventDispatcher<>(reKey);

    public static void init(FxSpringContext context) {
        FxAssert.isNull(beanContext, "不能重复初始化");
        beanContext = context;
    }

    public static void start() {
        for (Class<? extends FxJob> clazz : beanContext.getClassListForType(FxJob.class)) {
            JobComponent attr = clazz.getDeclaredAnnotation(JobComponent.class);
            if (attr != null) {
                start(clazz, attr.seconds());
            }
        }
    }

    public static void stop(Class<? extends FxJob> clazz) {
        stopJob.add(clazz);
    }

    public static void start(Class<? extends FxJob> clazz, int second) {
        Task.run(state -> {
            Runner runner = (Runner) state;
            long next = 0;
            while (FxEnv.isShutdown() == false) {
                if (stopJob.contains(runner.jobClass)) {
                    stopJob.remove(runner.jobClass);
                    break;
                }
                long t = next - System.currentTimeMillis();
                if (t <= 0) {
                    try {
                        runner.run();
                    } catch (Throwable e) {
                        if (e instanceof JobStopException) {
                            break;
                        } else {
                            logger.error("{}-执行异常", clazz.getTypeName(), e);
                        }
                    }
                    t = second * 1000L;
                    next = System.currentTimeMillis() + t;
                }
                Task.delay(Math.min(t, 60000));
            }
        }, new Runner(beanContext.child(), clazz)).onCompleted(t -> ((Runner) t.getState()).close());
    }

    private static class Runner {
        private Class<?> jobClass;
        private String lockey;
        private Lockspace lockspace;
        private Integer delayMillis;
        private BeanContext ctx;

        public Runner(BeanContext context, Class<?> clazz) {
            this.ctx = context;
            this.jobClass = clazz;
            JobComponent setting = jobClass.getAnnotation(JobComponent.class);
            if (setting != null) {
                if ("true".equalsIgnoreCase(setting.single()) || StringUtil.isNullOrEmpty(setting.single())) {
                    lockey = StringUtil.join("-", "timeJob", jobClass.getSimpleName(), jobClass.getCanonicalName().hashCode());
                } else if ("false".equalsIgnoreCase(setting.single()) == false) {
                    lockey = setting.single();
                }
                if (setting.randomDelay() > 0) {
                    delayMillis = RandomUtil.intByRange(0, setting.randomDelay() * 1000);
                }
            }
        }

        public void run() {
            if (delayMillis != null && delayMillis > 0) {
                Task.delay(delayMillis);
            }
            FxAutoCloseable ac = null;
            FxJob job = null;
            try {
                job = (FxJob) ctx.getBean(jobClass);
                if (lockey != null) {
                    if (lockspace == null) {
                        try {
                            lockspace = ctx.getBean(Lockspace.class);
                        } catch (Exception e) {
                            lockspace = new AppLockspace();
                        }
                    }
                    ac = lockspace.exLock(lockey);
                }
                RunBefore runBefore = new RunBefore(jobClass);
                evBefore.fire(rbKey, job, runBefore);
                if (runBefore.cancel) {
                    return;
                }
                job.run();
                RunAfter runAfter = new RunAfter(jobClass);
                evAfter.fire(raKey, job, runAfter);
            } catch (LockspaceException e) {
                job.locked();
            } catch (Exception e) {
                if (e instanceof JobStopException || job == null) {
                    throw e;
                }
                Exception rs = job.onError(e);
                if (rs != null) {
                    RunError runError = new RunError(jobClass, rs);
                    evError.fire(reKey, job, runError);
                    if (runError.error instanceof JobStopException) {
                        throw new JobStopException();
                    } else if (runError.error != null) {
                        throw ExUtil.toRuntime(runError.error);
                    }
                }
            } finally {
                if (ac != null) {
                    ac.close();
                }
            }
        }

        public void close() {
            ctx.close();
            ctx = null;
        }
    }
}
